#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project ：pytest_allure_pytest-html_study 
@File    ：05_分布式执行用例.py
@IDE     ：PyCharm 
@Author  ：XiaoDengYa
@Date    ：2021/6/3/003 14:54 
"""

"""
平常我们功能测试用例非常多时，比如有1千条用例，假设每个用例执行需要1分钟，如果单个测试人员执行需要1000分钟才能跑完
当项目非常紧急时，会需要协调多个测试资源来把任务分成两部分，于是执行时间缩短一半，如果有10个小伙伴，那么执行时间就会变成十分之一，大大节省了测试时间
为了节省项目测试时间，10个测试同时并行测试，这就是一种分布式场景
同样道理，当我们自动化测试用例非常多的时候， 一条条按顺序执行会非常慢，pytest-xdist的出现就是为了让自动化测试用例可以分布式执行，从而节省自动化测试时间
pytest-xdist是属于进程级别的并发
"""

"""
分布式执行用例的设计原则（重中之重的重点）
用例之间是独立的，用例之间没有依赖关系，用例可以完全独立运行【独立运行】
用例执行没有顺序，随机顺序都能正常执行【随机执行】
每个用例都能重复运行，运行结果不会影响其他用例【不影响其他用例】
"""

"""
pytest-xdist通过一些独特的测试执行模式扩展了pytest
测试运行并行化：如果有多个CPU或主机，则可以将它们用于组合的测试运行。 这样可以加快开发速度或使用远程计算机的特殊资源。
--looponfail：在子进程中重复运行测试。 每次运行之后，pytest都会等到项目中的文件更改后再运行之前失败的测试。 重复此过程，直到所有测试通过，然后再次执行完整运行。
跨平台覆盖：您可以指定不同的Python解释程序或不同的平台，并在所有这些平台上并行运行测试。
"""

# 使用分布式测试的命令和所需执行时间
# pytest -s -n auto

"""
可以指定需要多少个CPU来跑用例
pytest -s -n 2

pytest-xdist是可以和pytest-html很好的相结合
pytest -s -n auto --html=report.html --self-contained-html


pytest-xdist按照一定的顺序执行
 pytest-xdist默认是无序执行的，可以通过 --dist 参数来控制顺序
 --dist=loadscope 
将按照同一个模块module下的函数和同一个测试类class下的方法来分组，然后将每个测试组发给可以执行的worker，确保同一个组的测试用例在同一个进程中执行
目前无法自定义分组，按类class分组优先于按模块module分组
  --dist=loadfile 
按照同一个文件名来分组，然后将每个测试组发给可以执行的worker，确保同一个组的测试用例在同一个进程中执行
"""

"""

如何让scope=session的fixture在test session中仅仅执行一次
pytest-xdist是让每个worker进程执行属于自己的测试用例集下的所有测试用例

这意味着在不同进程中，不同的测试用例可能会调用同一个scope范围级别较高（例如session）的fixture，该fixture则会被执行多次，这不符合scope=session的预期

 

如何解决？
虽然pytest-xdist没有内置的支持来确保会话范围的夹具仅执行一次，但是可以通过使用锁定文件进行进程间通信来实现。

 

小栗子
下面的示例只需要执行一次login（因为它是只需要执行一次来定义配置选项，等等）
当第一次请求这个fixture时，则会利用FileLock仅产生一次fixture数据
当其他进程再次请求这个fixture时，则会从文件中读取数据
import pytest
from filelock import FileLock


@pytest.fixture(scope="session")
def login():
    print("====登录功能，返回账号，token===")
    with FileLock("session.lock"):
        name = "testyy"
        token = "npoi213bn4"
        # web ui自动化
        # 声明一个driver，再返回

        # 接口自动化
        # 发起一个登录请求，将token返回都可以这样写

    yield name, token
    print("====退出登录！！！====")
"""

"""
pytest-xdist分布式测试的原理
前言
xdist的分布式类似于一主多从的结构，master机负责下发命令，控制slave机；slave机根据master机的命令执行特定测试任务
在xdist中，主是master，从是workers
 

大致原理
xdist会产生一个或多个workers，workers都通过master来控制
每个worker负责执行完整的测试用例集，然后按照master的要求运行测试，而master机不执行测试任务
 

pytest-xdist分布式测试的流程
第一步：创建worker
master会在总测试会话（test session）开始前产生一个或多个worker
master和worker之间是通过execnet和网关来通信的
实际编译执行测试代码的worker可能是本地机器也可能是远程机器
 

第二步：收集测试项用例
每个worker类似一个迷你型的pytest执行器
worker会执行一个完整的test collection过程【收集所有测试用例的过程】
然后把测试用例的ids返回给master
master是不会执行任何测试用例集的
注意
所以为什么上面通过分布式测试的结果截图是没有输出用例的print内容，因为主机并不执行测试用例，pycharm相当于一个master

 

第三步：master检测workers收集到的测试用例集
master接收到所有worker收集的测试用例集之后，master会进行一些完整性检查，以确保所有worker都收集到一样的测试用例集（包括顺序）
如果检查通过，会将测试用例的ids列表转换成简单的索引列表，每个索引对应一个测试用例的在原来测试集中的位置
这个方案可行的原因是：所有的节点都保存着相同的测试用例集
并且使用这种方式可以节省带宽，因为master只需要告知workers需要执行的测试用例对应的索引，而不用告知完整的测试用例信息
 

第四步：测试用例分发
--dist-mode选项

each：master将完整的测试索引列表分发到每个worker

load：master将大约25%的测试用例以轮询的方式分发到各个worker，剩余的测试用例则会等待workers执行完测试用例以后再分发

 

注意
可以使用  pytest_xdist_make_scheduler  这个hook来实现自定义测试分发逻辑。

 

第五步：测试用例的执行
workers 重写了   pytest_runtestloop  ：pytest的默认实现是循环执行所有在test session这个对象里面收集到的测试用例
但是在xdist里, workers实际上是等待master为其发送需要执行的测试用例
当worker收到测试任务, 就顺序执行  pytest_runtest_protocol 
值得注意的一个细节是：workers 必须始终保持至少一个测试用例在的任务队列里, 以兼容  pytest_runtest_protocol(item, nextitem)   hook的参数要求，为了将 nextitem传给hook
worker会在执行最后一个测试项前等待master的更多指令
如果它收到了更多测试项, 那么就可以安全的执行   pytest_runtest_protocol  , 因为这时nextitem参数已经可以确定
如果它收到一个 "shutdown"信号, 那么就将 nextitem 参数设为 None, 然后执行 pytest_runtest_protocol
 

第六步：测试用例再分发（--dist-mode=load）
当workers开始/结束执行时，会把测试结果返回给master，这样其他pytest hook比如：  pytest_runtest_protocol  和  pytest_runtest_protocol  就可以正常执行
master在worker执行完一个测试后，基于测试执行时长以及每个work剩余测试用例综合决定是否向这个worker发送更多的测试用例
 

第七步：测试结束
当master没有更多执行测试任务时，它会发送一个“shutdown”信号给所有worker
当worker将剩余测试用例执行完后退出进程
master等待所有worker全部退出
然此时仍需要处理诸如  pytest_runtest_logreport  等事件
"""